with Ada.Text_IO;
package body optimaltemps is

   function getUnIndividuParmisListeUnIndividu(C0 : etat ; I1 : individu) return access un_individu is
      BorneInf : natural := C0'First;
      BorneSup : natural := C0'Last;
      Milieu : natural := (BorneSup + BorneInf) / 2 ;
   begin
      loop
         Milieu := (BorneSup + BorneInf) / 2 ;
         IF BorneInf <= BorneSup THEN
            if (C0(Milieu).all.son_nom.all = I1) then
               return C0(Milieu);
            else
               IF I1 <= C0(Milieu).all.son_nom.all then
                  BorneSup := Milieu -1;
               ELSE
                  BorneInf := Milieu + 1;
               END IF;
            end if ;

         ELSE
            return null;
         END IF;

      end loop;
   end getUnIndividuParmisListeUnIndividu;

   function getIndiceParmisListeUnIndividu(C0 : etat ; I1 : individu) return natural is
      BorneInf : natural := C0'First;
      BorneSup : natural := C0'Last;
      Milieu : natural := (BorneSup + BorneInf) / 2 ;
   begin
      loop
         Milieu := (BorneSup + BorneInf) / 2 ;
         IF BorneInf <= BorneSup THEN
            if (C0(Milieu).all.son_nom.all = I1) then
               return milieu;
            else

               IF I1 <= C0(Milieu).all.son_nom.all then
                  BorneSup := Milieu -1;
               ELSE
                  BorneInf := Milieu + 1;
               END IF;
            end if;

         ELSE
            return BorneSup;
         END IF;
      end loop;
   end getIndiceParmisListeUnIndividu;



   function getIndiceParmisListeMembre(C0 : access liste_membre ; I1 : individu) return natural is
      BorneInf : natural := C0.all'First;
      BorneSup : natural := C0.all'Last;
      Milieu : natural := (BorneSup + BorneInf) / 2 ;
   begin
      loop
         Milieu := (BorneSup + BorneInf) / 2 ;
         IF BorneInf <= BorneSup THEN
            if (C0(Milieu).all = I1) then
               return milieu;
            else

               IF I1 <= C0.all(Milieu).all then
                  BorneSup := Milieu -1;
               ELSE
                  BorneInf := Milieu + 1;
               END IF;
            end if;

         ELSE
            return BorneSup;
         END IF;
      end loop;
   end getIndiceParmisListeMembre;


   function getIndividuParmisListeMembre(C0 : access liste_membre ; I1 : individu) return access Individu is
      BorneInf : natural := C0.all'First;
      BorneSup : natural := C0.all'Last;
      Milieu : natural := (BorneSup + BorneInf) / 2 ;
   begin
      loop
         Milieu := (BorneSup + BorneInf) / 2 ;
         IF BorneInf <= BorneSup THEN
            if (C0(Milieu).all = I1) then
               return C0(Milieu);
            else
               IF I1 <= C0(Milieu).all then
                  BorneSup := Milieu -1;
               ELSE
                  BorneInf := Milieu + 1;
               END IF;
            end if ;

         ELSE
            return null;
         END IF;
      end loop;
   end getIndividuParmisListeMembre;


   function getIndiceParmisListeUnCercle(C0 : access liste_cercle ; C1 : cercle) return natural is
      BorneInf : natural := C0.all'First;
      BorneSup : natural := C0.all'Last;
      Milieu : natural := (BorneSup + BorneInf) / 2 ;
   begin
      loop
         Milieu := (BorneSup + BorneInf) / 2 ;
         IF BorneInf <= BorneSup THEN
            if (C0(Milieu).all.son_nom.all = C1) then
               return milieu;
            else

               IF C1 <= C0.all(Milieu).all.son_nom.all then
                  BorneSup := Milieu -1;
               ELSE
                  BorneInf := Milieu + 1;
               END IF;
            end if;

         ELSE
            return BorneSup;
         END IF;
      end loop;
   end getIndiceParmisListeUnCercle;

   function getCercleParmisListeUnCercle(C0 : access liste_cercle ; C1 : cercle) return access un_cercle is
      BorneInf : natural := C0.all'First;
      BorneSup : natural := C0.all'Last;
      Milieu : natural := (BorneSup + BorneInf) / 2 ;
   begin
      loop
         Milieu := (BorneSup + BorneInf) / 2 ;
         IF BorneInf <= BorneSup THEN
            if (C0.all(Milieu).all.son_nom.all = C1) then
               return C0.all(Milieu);
            else
               IF C1 <= C0.all(Milieu).all.son_nom.all then
                  BorneSup := Milieu -1;
               ELSE
                  BorneInf := Milieu + 1;
               END IF;
            end if ;

         ELSE
            return null;
         END IF;
      end loop;
   end getCercleParmisListeUnCercle;



   --------------
   ---- Creer----
   --------------


   function creer return modele is
   begin
      return (nb_post => 0,
              acces_etat => new etat(1..0));
   end creer;

   --------------
   -- inscrire --
   --------------


   function inscrire (C0:modele; I1:individu) return modele is
      l_etat : access etat := C0.acces_etat;
      l_individu_tableau : etat := (1=>new un_individu'(son_nom => new individu'(I1),
                                                        son_mur => new mur'(creer(I1)) ,
                                                        ses_cercles => new liste_cercle(1..0),
                                                        ses_bloques => new liste_membre(1..0)));
      indice : natural := getIndiceParmisListeUnIndividu(C0.acces_etat.all, I1) +1 ;
   begin
      l_etat := new etat'(l_etat.all(l_etat.all'first .. indice -1) & l_individu_tableau & l_etat.all(Indice..l_etat.all'last));
      return (nb_post => C0.nb_post,
              acces_etat => new etat'(l_etat.all));
   end inscrire;


   -----------------
   -- est_inscrit --
   -----------------

   function est_inscrit (C0:modele; I1:individu) return boolean is
   begin
      for i in C0.acces_etat.all'First .. C0.acces_etat.all'Last loop
         if C0.acces_etat.all(i).all.son_nom.all = I1 then return true; end if;
      end loop;
      return false;
   end est_inscrit;

   --------------------
   -- nombre_inscrit --
   --------------------

   function nombre_inscrit (C0:modele) return natural is
   begin
      return c0.acces_etat.all'Length;
   end nombre_inscrit;

   ------------------
   -- ieme_inscrit --
   ------------------

   function ieme_inscrit (C0:modele; I:Natural) return individu is
   begin
      return C0.acces_etat.all(I).all.son_nom.all;
   end ieme_inscrit;

   -------------
   -- bloquer --
   -------------

   function bloquer (C0:modele; I1,I2: individu) return modele is
      l_individu_tableau : access liste_membre := new liste_membre'(1 => new individu'(I2));
      l_individu_bloquant : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all, I1);
      les_individus : etat := C0.acces_etat.all;
      indice : natural := getIndiceParmisListeUnIndividu(C0.acces_etat.all, I1) ;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
   begin

      C.all.acces_etat.all(indice).all.ses_bloques :=
        new liste_membre'(l_individu_tableau.all
                          &
            C.all.acces_etat.all(indice).all.ses_bloques.all(
            C.all.acces_etat.all(indice).all.ses_bloques.all'First .. C.all.acces_etat.all(indice).all.ses_bloques.all'Last
           ));

      for i in l_individu_bloquant.all.ses_cercles.all'First .. l_individu_bloquant.all.ses_cercles.all'Last loop
         for j in l_individu_bloquant.all.ses_cercles.all(i).all.ses_membres.all'First .. l_individu_bloquant.all.ses_cercles.all(i).all.ses_membres.all'Last loop
            if l_individu_bloquant.all.ses_cercles.all(i).all.ses_membres.all(j).all = I2 then
               C.all:= retirer_membre(C.all,I1,C.all.acces_etat.all(i).all.ses_cercles.all(j).all.son_nom.all,I2);
            end if;
         end loop;
      end loop;
      return C.all;
   end bloquer;

   ---------------
   -- debloquer --
   ---------------

   function debloquer (C0:modele; I1,I2: individu) return modele is
      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu_bloquant : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
      indice : natural := getIndiceParmisListeMembre(l_individu_bloquant.all.ses_bloques,I2);
      indice_bloqueur : natural := getIndiceParmisListeUnIndividu(C.all.acces_etat.all,I1);

   begin
      C.all.acces_etat.all(indice_bloqueur).all.ses_bloques :=
        new liste_membre'(l_individu_bloquant.all.ses_bloques.all(l_individu_bloquant.all.ses_bloques.all'First  .. indice-1)
                          & l_individu_bloquant.all.ses_bloques.all(indice+1  .. l_individu_bloquant.all.ses_bloques.all'Last));
      return C.all;
   end debloquer;


   ----------------
   -- est_bloque --
   ----------------

   function est_bloque (C0:modele; I1,I2:individu) return boolean is
      l_individu_bloquant : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all, I1);
   begin
      for i in l_individu_bloquant.all.ses_bloques.all'First .. l_individu_bloquant.all.ses_bloques.all'Last loop
         if l_individu_bloquant.all.ses_bloques(i).all=I2 then return true; end if ;
      end loop;
      return false;
   end est_bloque;

   --------------------
   -- ajouter_cercle --
   --------------------

   function ajouter_cercle (C0:modele;I1: individu;C1:cercle)return modele is

      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
      le_cercle_tableau : liste_cercle := (1=> new un_cercle'(son_nom     => new cercle'(C1),
                                                              ses_membres => new liste_membre(1..0)));
      indice : natural := getIndiceParmisListeUnCercle(l_individu.all.ses_cercles,C1) +1 ;
   begin
      l_individu.all.ses_cercles := new liste_cercle'( l_individu.all.ses_cercles.all(l_individu.all.ses_cercles.all'First .. indice-1)
                                                      & le_cercle_tableau
                                                      & l_individu.all.ses_cercles.all(indice .. l_individu.all.ses_cercles.all'Last));
      return C.all;
   end ajouter_cercle;


   --------------------
   -- retirer_cercle --
   --------------------

   function retirer_cercle(C0:modele;I1: individu;C1:cercle) return modele is
      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
      indice : natural := getIndiceParmisListeUnCercle( l_individu.all.ses_cercles,C1);
   begin
       l_individu.all.ses_cercles := new liste_cercle'(  l_individu.all.ses_cercles.all( l_individu.all.ses_cercles.all'First .. indice -1) &
                                       l_individu.all.ses_cercles.all(indice +1  ..  l_individu.all.ses_cercles.all'Last));
      return C.all;
   end retirer_cercle;

   -------------------
   -- est_cercle_de --
   -------------------

   function est_cercle_de (C0:modele; I1:individu; C1:cercle) return boolean is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
      les_cercles : access liste_cercle := l_individu.all.ses_cercles;
   begin
      for i in les_cercles.all'First .. les_cercles.all'Last loop
         if les_cercles.all(i).all.son_nom.all = C1 then return true; end if;
      end loop;
      return false;
   end est_cercle_de;

   -----------------
   -- ieme_cercle --
   -----------------

   function ieme_cercle (C0:modele; I1:individu; I:natural) return cercle is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
      les_cercles : access liste_cercle := l_individu.all.ses_cercles;
   begin
      return les_cercles.all(I).all.son_nom.all;
   end ieme_cercle;



   --------------------
   -- ajouter_membre --
   --------------------

   function ajouter_membre(C0:modele;I1: individu;C1:cercle;I2: individu) return modele is
      l_individu_tableau : access liste_membre := new liste_membre'(1 => new individu'(I2));
      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
      indice_cercle : natural := getIndiceParmisListeUnCercle(l_individu.all.ses_cercles,C1);
      indice_membre : natural := getIndiceParmisListeMembre(l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres,I1)+1;
   begin
      l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres := new liste_membre'(
                                                                                         l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all(l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all'First .. indice_membre-1)
                                                                                         & l_individu_tableau.all
                                                                                         & l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all(indice_membre .. l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all'Last));

      return C.all;
   end ajouter_membre;


   --------------------
   -- retirer_membre --
   --------------------

   function retirer_membre(C0:modele;I1: individu; C1:cercle;I2: individu) return modele is

      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
indice_individu : natural := getIndiceParmisListeUnIndividu(C.all.acces_etat.all,I1);
      indice_cercle : natural := getIndiceParmisListeUnCercle(l_individu.all.ses_cercles,C1)  ;
      indice_membre : natural := getIndiceParmisListeMembre(l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres,I2);

   begin
      C.all.acces_etat.all(indice_individu).all.ses_cercles.all(indice_cercle).all.ses_membres := new liste_membre'(
                                                                                         l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all(l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all'First .. indice_membre-1)
                                                                                         & l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all(indice_membre+1.. l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all'Last));

      return C.all;
   end retirer_membre;






   ----------------------
   -- nombre_cercle_de --
   ----------------------

   function nombre_cercle_de (C0:modele; I1:individu) return natural is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
   begin
      return l_individu.all.ses_cercles.all'Length;
   end nombre_cercle_de;

   -------------------
   -- est_membre_de --
   -------------------

   function est_membre_de (C0:modele; I1:individu; C1:cercle; I2:individu) return boolean is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
      le_cercle : access un_cercle := getCercleParmisListeUnCercle(l_individu.all.ses_cercles,C1);

   begin
      for i in le_cercle.all.ses_membres.all'First .. le_cercle.all.ses_membres.all'Last loop
         if le_cercle.all.ses_membres.all(i).all = I2 then return true; end if;
      end loop;
      return false;
   end est_membre_de;

   ----------------------
   -- nombre_membre_de --
   ----------------------

   function nombre_membre_de (C0:modele; I1:individu; C1:cercle) return natural is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
      le_cercle : access un_cercle := getCercleParmisListeUnCercle(l_individu.all.ses_cercles,C1);
   begin
      return le_cercle.all.ses_membres.all'Length;
   end nombre_membre_de;

   -----------------
   -- ieme_membre --
   -----------------

   function ieme_membre(C0:modele;I1:individu;C1:cercle;I:natural) return individu is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
      le_cercle : access un_cercle := getCercleParmisListeUnCercle(l_individu.all.ses_cercles,C1);
   begin
      return le_cercle.all.ses_membres.all(I).all;
   end ieme_membre;






   ------------
   -- poster --
   ------------

   function poster (C0:modele;I1:individu; C1:cercle; T: message) return modele is

      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
      indice_cercle : natural := getIndiceParmisListeUnCercle(l_individu.all.ses_cercles,C1);
      nb_post : natural := nombre_post(C0);
   begin
      --ajout sur le mur de l'individu posteur
      l_individu.all.son_mur:= new mur'(ajouter(l_individu.all.son_mur.all,T,I1,nb_post+1));
      for i in l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all'First .. l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all'Last loop
         --pour chaque membre du cercle
         if not est_bloque(C0,l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all(i).all,I1) then
            DECLARE
               l_individu_mur : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,l_individu.all.ses_cercles.all(indice_cercle).all.ses_membres.all(i).all);
            BEGIN
               l_individu_mur.all.son_mur := new mur'(ajouter(l_individu_mur.all.son_mur.all,T,I1,nb_post+1));
            END ;
         end if;
      end loop;
      C.all.nb_post := C.all.nb_post +1;
      return C.all;
   end poster;

   ---------------
   -- commenter --
   ---------------

   function commenter(C0: modele;I1:individu;I:Natural;T: message) return modele is

      les_individus : etat := C0.acces_etat.all;
      C : access modele := new modele'(nb_post    => C0.nb_post,
                                       acces_etat => new etat'(les_individus));
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,I1);
      l_auteur : access un_individu := getUnIndividuParmisListeUnIndividu(C.all.acces_etat.all,auteur(l_individu.all.son_mur.all,I));
   begin
      for J in les_individus'First .. les_individus'Last loop

         if est_defini(les_individus(J).all.son_mur.all,I) then
            -- le post est sur le mur de l'individu J
            -- verification des blocages.
            if (not est_bloque(C0,les_individus(J).all.son_nom.all,I1)  -- le proprietaire n'a pas bloque l'auteur du com
                AND not est_bloque(C0,les_individus(J).all.son_nom.all,l_auteur.all.son_nom.all) -- le proprietaire n'a pas bloque lauteur du post
                AND not est_bloque(C0,I1,les_individus(J).all.son_nom.all)) then -- pas sur
               DECLARE
                  l_individu_mur : access un_individu := C.all.acces_etat.all(J);
               BEGIN
                  l_individu_mur.all.son_mur := new mur'(ajouter(l_individu_mur.all.son_mur.all,T,I1,I));
               END ;
            end if;
         end if;
      end loop;
      return C.all;
   end commenter;

   ---------------
   -- consulter --
   ---------------

   function consulter (C0:modele; I1:individu) return Mur is
      l_individu : access un_individu := getUnIndividuParmisListeUnIndividu(C0.acces_etat.all,I1);
   begin
      return l_individu.son_mur.all;
   end consulter;

   -----------------
   -- nombre_post --
   -----------------

   function nombre_post (C0:modele) return Natural is
   begin
      return C0.nb_post;
   end nombre_post;

end optimaltemps;
